<?php
/**
 * FCoin 平台接口类
 * User: zengdeshun
 * Date: 2018/5/12
 * Time: 14:10
 */

namespace platform\driver\fcoin;

use Workerman\Connection\AsyncTcpConnection;
use platform\driver\Driver;
use GuzzleHttp\Client as Client;
use exception\Fcoin as FcoinException;
use Workerman\Lib\Timer;
use think\Log;

class Fcoin extends Driver
{
    /**
     * rest api接口地址
     * @var string
     */
    private $api = '';

    /**
     * webSocket接口地址
     * @var string
     */
    private $stream = '';

    /**
     * API 访问密钥
     * @var string
     */
    private $accessKey = '';

    /**
     * 签名密钥
     * @var string
     */
    private $secretKey = '';

    /**
     * api请求路径
     * @var string
     */
    private $apiMethod = '';

    /**
     * api请求方法
     * @var string
     */
    private $reqMethod = '';

    public function __construct($param = []) {
        $config = file_get_contents(dirname(__FILE__) . '/config.json');
        $config = json_decode($config, true);
        $this->api = $config['api'];
        $this->stream = $config['stream'];
        if (isset($param['access_key']) && isset($param['secret_key'])) {
            $this->accessKey = $param['access_key'];
            $this->secretKey = $param['secret_key'];
        }
    }

    /**
     * curl请求客户端
     * @return Client
     */
    private function client()
    {
        return new Client(['base_uri' => $this->api]);
    }

    /**
     * 下单接口
     * @param mixed ...$data
     * @return mixed
     * @throws \Exception
     */
    public function buy(...$data)
    {
        list($side, $symbol, $quantity, $price, $type) = $data;
        $this->apiMethod = 'orders';
        $this->reqMethod = 'POST';
        $timestamp = get_microseconds();
        $symbol = str_replace('_','',$symbol);
        $param = [
            'symbol' => $symbol,
            'side'   => $side,
            'type'   => $type,
            'amount' => $quantity
        ];
        if ($type == 'limit') {
            $param['price'] = $price;
        }
        try {
            ksort($param);
            $sign = $this->createSign($this->reqMethod, $this->apiMethod, $timestamp, $param);
            $header = $this->getHeaders($this->accessKey, $sign, $timestamp);
            $response = curl($this->api . $this->apiMethod, json_encode($param), $this->reqMethod, $header);
            $response = json_decode($response, true);
        } catch (\Exception $exception){
            Log::record('Fcoin下单失败 ' . $exception->getMessage());
            exception(FcoinException::getErrorMsg(FcoinException::E_DEFAULT), FcoinException::E_DEFAULT, 'exception\Fcoin');
        }

        if ($response['status'] != '0') {
            $errorCode = isset($this->_errorCode[$response['status']]) ? $this->_errorCode[$response['status']] : 0;
            if (!$errorCode) {
                $msg = isset($response['msg']) ? $response['msg'] : FcoinException::getErrorMsg(FcoinException::E_DEFAULT);
                Log::record('Fcoin下单失败 ' . $msg);
                exception($msg, FcoinException::E_DEFAULT, 'exception\Fcoin');
            }
            Log::record('Fcoin下单失败，错误码' . $errorCode . '错误信息' . $errorCode);
            exception(FcoinException::getErrorMsg($errorCode), $errorCode, 'exception\Fcoin');
        }
        return $response['data'];
    }

    /**
     * 申请撤销一个订单请求
     * @param mixed ...$data
     * @return mixed
     * @throws \Exception
     */
    public function cancel(...$data)
    {
        list($orderId) = $data;
        $this->apiMethod = "orders/{$orderId}/submit-cancel";
        $this->reqMethod = 'POST';
        $timestamp = get_microseconds();
        $sign = $this->createSign($this->reqMethod, $this->apiMethod, $timestamp);
        $header = $this->getHeaders($this->accessKey, $sign, $timestamp);
        $response = curl($this->api . $this->apiMethod, [], $this->reqMethod, $header);
        if (empty($response)) {
            Log::record('Fcoin取消订单失败');
            exception(FcoinException::getErrorMsg(FcoinException::E_DEFAULT), FcoinException::E_DEFAULT, 'exception\Fcoin');
        }
        $response = json_decode($response, true);
        if ($response['status'] != '0') {
            $errorCode = isset($this->_errorCode[$response['status']]) ? $this->_errorCode[$response['status']] : 0;
            if (!$errorCode) {
                $msg = isset($response['msg']) ? $response['msg'] : FcoinException::getErrorMsg(FcoinException::E_DEFAULT);
                Log::record('Fcoin取消订单失败 ' . $msg);
                exception($msg, FcoinException::E_DEFAULT, 'exception\Fcoin');
            }
            Log::record('Fcoin取消订单失败，错误码' . $errorCode . '错误信息' . $errorCode);
            exception(FcoinException::getErrorMsg($errorCode), $errorCode, 'exception\Fcoin');
        }
        return isset($response['data']) ? $response['data'] : true;
    }

    /**
     * 查询某个订单详情
     * @param mixed ...$data
     * @return array
     * @throws \Exception
     */
    public function order(...$data)
    {
        list($orderId) = $data;
        $this->apiMethod  = "orders/{$orderId}";
        $this->reqMethod = 'GET';
        $timestamp = get_microseconds();
        $sign = $this->createSign($this->reqMethod, $this->apiMethod, $timestamp);
        $header = $this->getHeaders($this->accessKey, $sign, $timestamp);
        $response = curl($this->api . $this->apiMethod, [], $this->reqMethod, $header);
        $response = json_decode($response, true);
        if ($response['status'] != '0') {
            $errorCode = isset($this->_errorCode[$response['status']]) ? $this->_errorCode[$response['status']] : 0;
            if (!$errorCode) {
                $msg = isset($response['msg']) ? $response['msg'] : FcoinException::getErrorMsg(FcoinException::E_DEFAULT);
                Log::record('Fcoin取消订单失败 ' . $msg);
                exception($msg, FcoinException::E_DEFAULT, 'exception\Fcoin');
            }
            Log::record('Fcoin取消订单失败，错误码' . $errorCode . '错误信息' . $errorCode);
            exception(FcoinException::getErrorMsg($errorCode), $errorCode, 'exception\Fcoin');
        }

        return $response['data'];
    }

    /**
     * 查询交易对
     * @return mixed
     * @throws \Exception
     */
    public function symbols()
    {
        $this->apiMethod  = 'public/symbols';
        $this->reqMethod = 'GET';
        $response = $this->client()->request($this->reqMethod, $this->apiMethod);
        $result  = $response->getBody()->getContents();
        return $result;
    }

    /**
     * 查询账户的资产
     * @return mixed
     * @throws \Exception
     */
    public function balance()
    {
        $this->apiMethod = 'accounts/balance';
        $this->reqMethod = 'GET';
        $timestamp = get_microseconds();
        $sign = $this->createSign($this->reqMethod, $this->apiMethod, $timestamp);
        $header = $this->getHeaders($this->accessKey, $sign, $timestamp);
        $response = curl($this->api . $this->apiMethod, [], $this->reqMethod, $header);
        $response = json_decode($response, true);
        if ($response['status'] != '0') {
            $errorCode = isset($this->_errorCode[$response['status']]) ? $this->_errorCode[$response['status']] : 0;
            if (!$errorCode) {
                $msg = isset($response['msg']) ? $response['msg'] : FcoinException::getErrorMsg(FcoinException::E_DEFAULT);
                Log::record('Fcoin取消订单失败 ' . $msg);
                exception($msg, FcoinException::E_DEFAULT, 'exception\Fcoin');
            }
            Log::record('Fcoin取消订单失败，错误码' . $errorCode . '错误信息' . $errorCode);
            exception(FcoinException::getErrorMsg($errorCode), $errorCode, 'exception\Fcoin');
        }
        return $response['data'];
    }

    /**
     * 参数签名
     * @param $params
     * @return mixed
     */
    private function createSign($method, $url, $time, $data = [])
    {
        $uri = $this->api . $url;
        $body = "";
        if(!empty($data))
        {
            //ksort($data);
            $body = http_build_query($data);
        }
        $signature = "";
        $signature .= $method . $uri ;
        if(strcmp('GET',$method) == 0) {
            if($body != ""){
                $signature .= '?' . $body .$time;
            }else{
                $signature .= $time;
            }
        } else {
            $signature .= $time . $body;
        }
        $signature = base64_encode($signature);
        $signature = hash_hmac('sha1', $signature, $this->secretKey, true);
        $signature = base64_encode($signature);
        return $signature;
    }

    /**
     * 获取http请求头
     * @param $key
     * @param $signature
     * @param $time
     * @return array
     */
    private function getHeaders($key, $signature, $time)
    {
        return [
            "FC-ACCESS-KEY:$key",
            "FC-ACCESS-SIGNATURE:$signature",
            "FC-ACCESS-TIMESTAMP:$time",
            "Content-Type:application/json;charset=UTF-8"
        ];
    }

    /**
     * webSocket订阅ticker数据
     * @param $symbols
     * @param $callback
     * @return bool
     */
    public function tickerWebSocket($symbols, $callback)
    {
        $args = [];
        foreach ($symbols as $key => $symbol) {
            $args[] = "ticker.{$symbol}";
        }
        $param = [
            'cmd'   => 'sub',
            'args'  => $args,
            'topic' => ['ticker'],
            'id'    => get_microseconds()
        ];
        $this->webSocketClient($param, $callback);
        return true;
    }

    /**
     * 订阅市场深度
     * @param $symbols
     * @param $callback
     * @return bool
     * @throws \Exception
     */
    public function depthWebSocket($symbols, $level, $callback)
    {
        $args = [];
        foreach ($symbols as $key => $symbol) {
            $args[] = "depth.{$level}.{$symbol}";
        }
        $param = [
            'cmd'   => 'sub',
            'args'  => $args,
            'topic' => ['depth'],
            'id'    => get_microseconds()
        ];
        $this->webSocketClient($param, $callback);
        return true;
    }

    /**
     * webSocket接口统一请求客户端
     * @param $param
     * @param $callback
     * @return bool
     * @throws \Exception
     */
    protected function webSocketClient($param, $callback)
    {
        $con = new AsyncTcpConnection($this->stream);
        $con->transport = 'ssl'; // 设置以ssl加密方式访问，使之成为wss
        $con->onConnect = function($con) use ($param) {
            $con->send(json_encode($param));

            // 设置定时器向服务器发送心跳数据
            $con->timerId = Timer::add(20, function() use ($con) {
                $time = get_microseconds();
                $heartbeat = [
                    'cmd'   => 'ping',
                    'args'  => [$time],
                    'topic' => ['ping'],
                    'id'    => $time
                ];

                $con->send(json_encode($heartbeat));
            });
        };

        $con->onMessage = function($con, $data) use ($callback) {
            $data = json_decode($data, true);
            if ($data['type'] != 'ping') {
                call_user_func_array($callback, array($data));
            }
        };

        $con->connect();
        return true;
    }

    /**
     * 错误码映射
     * @var array
     */
    private $_errorCode =[
        '3016' => '28002',
        '3014' => '28002',
        '3020' => '28002',
        '3033' => '28003',
        '6005' => '28004'
    ];

    /**
     * REST方式获取行情
     * @return array
     */
    public function restTicker($symbol)
    {
        $data = [];
        try {
            $headers = [
                'Content-Type: application/x-www-form-urlencoded',
            ];

            $data = curl($this->api . 'market/ticker/' . $symbol, [], 'GET', $headers);
            $data = json_decode($data, true);
        } catch (\Exception $e) {
            Log::record('初始化OKEx请求行情数据出错' . $e->getMessage());
        }

        return $data;
    }

    /**
     * REST方式获取深度
     * @return array
     */
    public function restDepth($symbol, $level)
    {
        $data = [];
        try {
            $headers = [
                'Content-Type: application/x-www-form-urlencoded',
            ];

            $data = curl($this->api . 'market/depth/' . $level . '/' . $symbol, [], 'GET', $headers);
            $data = json_decode($data, true);
        } catch (\Exception $e) {
            Log::record('初始化OKEx请求行情数据出错' . $e->getMessage());
        }

        return $data;
    }
}
